#include "stdafx.h"
#include "Ribbons.h"

#include "Engine.h"
#include "Filesystem.h"
#include "GpuBufferManipulator.h"
#include "Textgen.h"

const uint tris_per_ribbon = 6000;
const uint n_quads = 1000;
const uint tunnel_segments = 32;
const Color ribbon1_color = Color(12, 32, 6, 210);
const Color ribbon2_color = Color(42, 15, 8, 210);
const Color ribbon3_color = Color(13, 10, 28, 210);

Ribbons::Ribbons(RendererPtr renderer, VectorBatchPtr vector_batch, SpriteBatchPtr sprite_batch, BitmapFontPtr font)
: renderer(renderer), vector_batch(vector_batch), sprite_batch(sprite_batch), font(font), prng(1337),
  camera(Vector3(0.0f, 0.0f, 0.0f), Vector3(0.0f, 0.0f, 1.0f), Vector3(0.0f, 1.0f, 0.0f))
{
}

Ribbons::~Ribbons() 
{
}

void Ribbons::init(boost::weak_ptr<Demo> owner)
{
	this->owner = owner;

	uint viewport_width, viewport_height;
	video_device->getViewport(viewport_width, viewport_height);
	/*
	offscreen_target1 = video_device->getTextureManager()->createRenderTarget("rt_ribbons1", 
		viewport_width/4, viewport_height/4, D3DFMT_A8R8G8B8);
	offscreen_target2 = video_device->getTextureManager()->createRenderTarget("rt_ribbons2", 
		viewport_width/4, viewport_height/4, D3DFMT_A8R8G8B8);
	*/
	offscreen_target1 = video_device->getTextureManager()->getRenderTarget("rt_glow0");
	offscreen_target2 = video_device->getTextureManager()->getRenderTarget("rt_glow1");
	pattern1 = video_device->getTextureManager()->getTexture2D("pattern1");
	default_sprite_effect = video_device->getEffectManager()->get("SpriteBatch");
	blured_sprite_effect = video_device->getEffectManager()->get("BluredSpriteBatch");

	text_target = video_device->getTextureManager()->createRenderTarget("rt_tunnel_text",
		2048, 1024, D3DFMT_A8R8G8B8);

	// Ribbons
	ribbons_effect = video_device->getEffectManager()->load("Ribbons", Filesystem::redirect("Ribbons.fx"));
	tunnel_effect = video_device->getEffectManager()->load("Tunnel", Filesystem::redirect("Tunnel.fx"));
	tunnel_effect->setParamValue(tunnel_effect->getParamBySemantic("TUNNEL_TEXTURE"), boost::shared_polymorphic_cast<BaseTexture>(text_target)); 

	ribbons_vb = video_device->getGpuBufferManager()->createVertexBuffer("ribbons_vb", 
		VertexPosColor::getFormat(), tris_per_ribbon * 3 * 3, false);

	//GpuBufferManipulator<VertexPosColor> ribbons_manipulator(ribbons_vb);
	LockedBuffer lb = ribbons_vb->lock(false);
	VertexPosColor* data = reinterpret_cast<VertexPosColor*>(lb.data);

	for(uint idx = 0; idx < tris_per_ribbon * 3 * 3; idx += 3 * 3) 
	{
		float t = float(idx/9) / float(tris_per_ribbon);

		Vector3 ribbon1 = curve1(t);
		Vector3 ribbon2 = curve2(t);
		Vector3 ribbon3 = curve3(t);

		data[idx] = VertexPosColor(ribbon1 + random_point_in_sphere(), ribbon1_color);
		data[idx+1] = VertexPosColor(ribbon1 + random_point_in_sphere(), ribbon1_color);
		data[idx+2] = VertexPosColor(ribbon1 + random_point_in_sphere(), ribbon1_color);

		data[idx+3] = VertexPosColor(ribbon2 + random_point_in_sphere(), ribbon2_color);
		data[idx+4] = VertexPosColor(ribbon2 + random_point_in_sphere(), ribbon2_color);
		data[idx+5] = VertexPosColor(ribbon2 + random_point_in_sphere(), ribbon2_color);

		data[idx+6] = VertexPosColor(ribbon3 + random_point_in_sphere(), ribbon3_color);
		data[idx+7] = VertexPosColor(ribbon3 + random_point_in_sphere(), ribbon3_color);
		data[idx+8] = VertexPosColor(ribbon3 + random_point_in_sphere(), ribbon3_color);
	}
	ribbons_vb->unlock();
	//ribbons_manipulator.apply();

	ribbons_batch.effect_id = ribbons_effect->getId();
	ribbons_batch.pass = 0;
	ribbons_batch.priority = 1010;
	ribbons_batch.vb_id = ribbons_vb->getId();

	// Quads
	quads_effect = video_device->getEffectManager()->load("TunnelRects", Filesystem::redirect("TunnelRects.fx"));
	quads_ib = video_device->getGpuBufferManager()->createIndexBuffer("quads_ib", true, n_quads*6, false);
	LockedBuffer locked_ib = quads_ib->lock(false);
	uint16* ib_data = reinterpret_cast<uint16*>(locked_ib.data);
	for(uint i = 0, v = 0; i < n_quads * 6; i += 6, v += 4)
	{
		ib_data[i] = v;
		ib_data[i+1] = v+1;
		ib_data[i+2] = v+2;
		ib_data[i+3] = v+2;
		ib_data[i+4] = v+1;
		ib_data[i+5] = v+3;
	}
	quads_ib->unlock();

	quads_vb = video_device->getGpuBufferManager()->createVertexBuffer("quads_vb", VertexPosColor::getFormat(),
		n_quads * 4, false);
	LockedBuffer locked_vb = quads_vb->lock(false);
	VertexPosColor* vb_data = reinterpret_cast<VertexPosColor*>(locked_vb.data);
	for(uint i = 0; i < n_quads; ++i)
	{
		float t = prng.randFloat(-50.0f, 250.0f);
		float a = prng.randFloat(0.0f, pi*2.0f);
		float l = prng.normal(3.0f, 1.0f);
		float r = 6.0f + prng.randFloat(-0.7f, 0.7f);
		float w = 1.0f;

		Vector3 to_center(0.0f, cos(a)*r, sin(a)*r);
		Vector3 forward(1.0f, 0.0f, 0.0f);
		Vector3 down = to_center.cross(forward);
		down.normalize();

		w /= 2.0f; l /= 2.0f;
		to_center.x = t;
		Vector3 v1 = to_center + (forward * l) + (down * w);
		Vector3 v2 = to_center - (forward * l) + (down * w);
		Vector3 v3 = to_center - (forward * l) - (down * w);
		Vector3 v4 = to_center + (forward * l) - (down * w);

		Color c(prng.randInt(100, 130), prng.randInt(70, 90), prng.randInt(10, 50));
		vb_data[i*4] = VertexPosColor(v1, c);
		vb_data[i*4+1] = VertexPosColor(v2, c);
		vb_data[i*4+2] = VertexPosColor(v4, c);
		vb_data[i*4+3] = VertexPosColor(v3, c);
	}
	quads_vb->unlock();

	quads1_batch.effect_id = quads_effect->getId();
	quads1_batch.pass = 0;
	quads1_batch.technique = 0;
	quads1_batch.priority = 1005;
	quads1_batch.ib_id = quads_ib->getId();
	quads1_batch.vb_id = quads_vb->getId();
	quads1_batch.tri_count = n_quads * 2;

	quads2_batch.effect_id = quads_effect->getId();
	quads2_batch.pass = 0;
	quads2_batch.technique = 1;
	quads2_batch.priority = 1000;
	quads2_batch.ib_id = quads_ib->getId();
	quads2_batch.vb_id = quads_vb->getId();
	quads2_batch.tri_count = n_quads * 2;

	// Tunnel
	tunnel_ib = video_device->getGpuBufferManager()->createIndexBuffer("tunnel_ib", true, tunnel_segments*6, false);
	locked_ib = tunnel_ib->lock(false);
	ib_data = reinterpret_cast<uint16*>(locked_ib.data);
	//float da = 2.0f * pi / float(tunnel_segments);
	for(uint16 segment = 0; segment < tunnel_segments; ++segment)
	{
		ib_data[segment*6] = segment*2;
		ib_data[segment*6+1] = segment*2+1;
		ib_data[segment*6+2] = segment*2+3;
		ib_data[segment*6+3] = segment*2;
		ib_data[segment*6+4] = segment*2+3;
		ib_data[segment*6+5] = segment*2+2;
	}
	tunnel_ib->unlock();

	tunnel_vb = video_device->getGpuBufferManager()->createVertexBuffer("tunnel_vb", VertexPosUV::getFormat(),
		(tunnel_segments+1) * 2, false);
	locked_vb = tunnel_vb->lock(false);
	VertexPosUV* tvb_data = reinterpret_cast<VertexPosUV*>(locked_vb.data);
	for(uint i = 0; i <= tunnel_segments; ++i) 
	{
		float t = float(i) / float(tunnel_segments);
		float a = 2.0f * pi * t;

		tvb_data[i*2] = VertexPosUV(Vector3(0.0f, cos(a) * 10.0f, sin(a) * 10.0f), 0.0f, t);
		tvb_data[i*2+1] = VertexPosUV(Vector3(250.0f, cos(a) * 10.0f, sin(a) * 10.0f), 1.0f, t);
	}
	tunnel_vb->unlock();

	tunnel1_batch.effect_id = tunnel_effect->getId();
	tunnel1_batch.pass = 0;
	tunnel1_batch.technique = 0;
	tunnel1_batch.priority = 999;
	tunnel1_batch.ib_id = tunnel_ib->getId();
	tunnel1_batch.vb_id = tunnel_vb->getId();
	tunnel1_batch.tri_count = tunnel_segments * 2;

	tunnel2_batch.effect_id = tunnel_effect->getId();
	tunnel2_batch.pass = 0;
	tunnel2_batch.technique = 1;
	tunnel2_batch.priority = 998;
	tunnel2_batch.ib_id = tunnel_ib->getId();
	tunnel2_batch.vb_id = tunnel_vb->getId();
	tunnel2_batch.tri_count = tunnel_segments * 2;

	while(text.size() < 2000)
	{
		text += textgenWord() + " ";
	}
}

void Ribbons::close() 
{
}

void Ribbons::update(float t)
{
	float transformed_t = transformT(21.0f, 44.0f, t);
	Vector3 tg = curve1(transformed_t) + curve2(transformed_t) + curve3(transformed_t);
	Vector3 p = curve1(transformed_t-0.08f) + curve2(transformed_t-0.08f) + curve3(transformed_t-0.08f);
	p *= 0.333f; tg *= 0.333f;
	p.z -= 3.0f + sin(t)*2.0f;

	ribbons_batch.tri_count = uint(float(tris_per_ribbon*3) * transformed_t); 
	ribbons_batch.tri_count = max(1, ribbons_batch.tri_count);

	camera.setPosition(smoothstep(Vector3(transformed_t * 160.0f, 0.0f, 0.0f), p, transformT(25.5f, 32.0f, t)));
	tg -= p;
	camera.setDirection(smoothstep(Vector3(1.0f, 0.0f, 0.0f), tg, transformT(25.5f, 32.0f, t)));
	//camera.setPosition(Vector3(0.0f, 0.0f, 0.0f));
	//camera.setDirection(Vector3(1.0f, 0.0f, 0.0f));
}

void Ribbons::render(float t)
{
	static bool text_rendered = false;
	if(!text_rendered) 
	{
		// Text
		video_device->setClearColor(Color(255, 255, 255, 0));
		video_device->beginScene(true, false, false, text_target);
		sprite_batch->setVirtualResolution(2048, 512);
		font->drawToRect(RectF(0.0f, 0.0f, 2048.0f, 512.0f), text);
		sprite_batch->present();
		renderer->render();
		video_device->endScene();
		text_rendered = true;
	}

	video_device->getRenderingParams().setCamera(camera);
	video_device->getRenderingParams().setPerBatch(Matrix::identity(), 0);
	
	ribbons_effect->applyAutoParams();
	quads_effect->applyAutoParams();
	tunnel_effect->applyAutoParams();

	// Glow buffer
	video_device->setClearColor(Color(0, 0, 0));
	video_device->beginScene(true, false, false, offscreen_target1);
	renderer->prepare(ribbons_batch);
	renderer->render(1010, 1010);
	video_device->endScene();

	// Blur 1
	sprite_batch->setVirtualResolution(1024/4, 576/4);
	sprite_batch->setEffect(blured_sprite_effect);
	video_device->beginScene(true, false, false, offscreen_target2);
	sprite_batch->draw(offscreen_target1, 200, RectF(), Vector2(0.0f, 0.0f));
	sprite_batch->present(0);
	renderer->render(1200, 1200);
	video_device->endScene();

	// Blur 2
	video_device->beginScene(true, false, false, offscreen_target1);
	sprite_batch->draw(offscreen_target2, 300, RectF(), Vector2(0.0f, 0.0f));
	sprite_batch->present(1);
	renderer->render(1300, 1300);
	video_device->endScene();

	// Blur 3
	video_device->beginScene(true, false, false, offscreen_target2);
	sprite_batch->draw(offscreen_target1, 400, RectF(), Vector2(0.0f, 0.0f));
	sprite_batch->present(0);
	renderer->render(1400, 1400);
	video_device->endScene();

	// Blur 4
	video_device->beginScene(true, false, false, offscreen_target1);
	sprite_batch->draw(offscreen_target2, 500, RectF(), Vector2(0.0f, 0.0f));
	sprite_batch->present(1);
	renderer->render(1500, 1500);
	video_device->endScene();

	// Blur 5
	video_device->beginScene(true, false, false, offscreen_target2);
	sprite_batch->draw(offscreen_target1, 600, RectF(), Vector2(0.0f, 0.0f));
	sprite_batch->present(0);
	renderer->render(1600, 1600);
	video_device->endScene();

	// Blur 6
	video_device->beginScene(true, false, false, offscreen_target1);
	sprite_batch->draw(offscreen_target2, 700, RectF(), Vector2(0.0f, 0.0f));
	sprite_batch->present(1);
	renderer->render(1700, 1700);
	video_device->endScene();
	
	// Final output
	video_device->beginScene();
	renderer->prepare(quads1_batch);
	renderer->prepare(quads2_batch);
	renderer->prepare(tunnel1_batch);
	renderer->prepare(tunnel2_batch);
	sprite_batch->setVirtualResolution(1024, 576);
	sprite_batch->setEffect(default_sprite_effect);
	sprite_batch->draw(offscreen_target1, 9, RectF(), RectF(0.0f, 0.0f, 1024.0f, 576.0f),
		Color::smoothstep(Color(255, 255, 255, 128), Color(255, 255, 255, 255), transformT(43.0f, 44.0f, t)));
	sprite_batch->present(1);
	if(t > 42.0f)
	{
		static std::vector<Vector2> fullscr_rect;
		static bool rect_filled = false;
		if(!rect_filled)
		{
			fullscr_rect.push_back(Vector2(0.0f, 0.0f));
			fullscr_rect.push_back(Vector2(1024.0f, 0.0f));
			fullscr_rect.push_back(Vector2(1024.0f, 576.0f));
			fullscr_rect.push_back(Vector2(0.0f, 576.0f));
			rect_filled = true;
		}
		vector_batch->drawPolygon(11, fullscr_rect, 
			Color::smoothstep(Color(64, 64, 64, 0), Color(0, 0, 0, 255), transformT(42.0f, 44.0f, t)));
		vector_batch->present();
	}
	renderer->render(900, 1100);
	video_device->endScene();
	renderer->endFrame();
}

Vector3 Ribbons::curve1(float t)
{
	Vector3 p;

	p.x = t * 200.0f;
	p.y = cos(t * 21.0f) * 2.0f;
	p.z = sin(t * 23.5f) * 2.0f;

	if(t > 0.88f) {
		p = smoothstep(p, Vector3(p.x, 0.0f, 0.0f), (t - 0.88f) / 0.08f);
	}

	return p;
}

Vector3 Ribbons::curve2(float t)
{
	Vector3 p;

	p.x = t * 210.0f;
	p.y = cos(t * 24.2f + pi * 2.0f/3.0f) * 2.0f;
	p.z = sin(t * 26.0f + pi * 2.0f/3.0f) * 2.0f;

	if(t > 0.88f) {
		p = smoothstep(p, Vector3(p.x, 0.0f, 0.0f), (t - 0.88f) / 0.08f);
	}

	return p;
}

Vector3 Ribbons::curve3(float t)
{
	Vector3 p;

	p.x = t * 190.0f;
	p.y = cos(-0.4f + t * 38.0f + pi * 4.0f/3.0f) * 2.0f;
	p.z = sin(-0.4f + t * 32.8f + pi * 4.0f/3.0f) * 2.0f;

	if(t > 0.88f) {
		p = smoothstep(p, Vector3(p.x, 0.0f, 0.0f), (t - 0.88f) / 0.08f);
	}

	return p;
}

Vector3 Ribbons::random_point_in_sphere()
{
	Vector3 point;
	do
	{
		point = Vector3(prng.randFloat(-1.0f, 1.0f), prng.randFloat(-1.0f, 1.0f), prng.randFloat(-1.0f, 1.0f));
	} while(point.x*point.x + point.y*point.y + point.z*point.z > 1.0f);	
	return point;
}